package com.pi.stroop.service;

import com.pi.base.enumerate.user.Gender;
import com.pi.base.exception.ServiceException;
import com.pi.base.util.time.DateTools;
import com.pi.stroop.dao.entity.StroopDiagnosisRecordEntity;
import com.pi.stroop.dao.entity.StroopInvitationEntity;
import com.pi.stroop.dao.entity.StroopTaskRecordEntity;
import com.pi.stroop.dao.mapper.StroopDiagnosisRecordMapper;
import com.pi.stroop.dao.param.StroopDiagnosisRecordParam;
import com.pi.stroop.enumerate.DiagnosisLevelEnum;
import com.pi.stroop.enumerate.DiagnosisResponseState;
import com.pi.stroop.enumerate.DiagnosisStageEnum;
import com.pi.stroop.model.diagnosis.DiagnosisDetailResult;
import com.pi.stroop.model.diagnosis.DiagnosisFullResult;
import com.pi.stroop.model.diagnosis.DiagnosisNode;
import com.pi.stroop.model.diagnosis.DiagnosisResult;
import com.pi.stroop.model.diagnosis.DiagnosisTask;
import com.pi.stroop.model.diagnosis.DiagnosisTaskBatchEntity;
import com.pi.stroop.model.diagnosis.DiagnosisTaskDetail;
import com.pi.stroop.model.diagnosis.RootNode;
import com.pi.stroop.model.dto.StroopTaskRecordPostForm;
import com.pi.uc.dao.entity.UcUserEntity;
import com.pi.uc.service.UcUserService;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.validator.constraints.NotBlank;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.annotation.Validated;

import javax.validation.constraints.Max;
import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;

@Validated
@Service
public class StroopDiagnosisService {
    @Autowired
    private StroopInvitationService invitationServce;
    @Autowired
    private StroopDiagnosisRecordMapper diagnosisRecordMapper;
    @Autowired
    private StroopDiagnosisTaskService diagnosisTaskService;
    @Autowired
    private UcUserService userService;


    public List<StroopDiagnosisRecordEntity> queryUserDiagnosisHistory(
            @NotNull(message = "STROOP_RECORD.USER_ID_IS_EMPTY") Long userId,
            @Max(value = 100, message = "STROOP_RECORD.PAGE_SIZE_TOO_LARGE") Integer pageSize,
            @Max(value = 1000, message = "STROOP_RECORD.START_ROW_TOO_LARGE") Integer startRow) {
        if (null == pageSize || pageSize <= 0) {
            pageSize = 20;
        }
        if (null == startRow || startRow < 0) {
            startRow = 0;
        }
        //查询我的邀请记录
        List<StroopInvitationEntity> invitationList =
                invitationServce.queryUserInvitation(userId);
        List<Long> inviteIds = getUserInvites(invitationList);
        //查询我和邀请用户的记录
        return diagnosisRecordMapper.queryUserDiagnosisHistory(userId, inviteIds, pageSize, startRow);
    }

    /**
     * 根据用户id查询他能看到的记录
     *
     * @return
     */
    public List<StroopDiagnosisRecordEntity> queryPartnerDiagnosisHistory(
            @NotNull(message = "STROOP_RECORD.USER_ID_IS_EMPTY") Long userId,
            @Max(value = 500, message = "STROOP_RECORD.PAGE_SIZE_TOO_LARGE") Integer pageSize,
            @Max(value = 1000, message = "STROOP_RECORD.START_ROW_TOO_LARGE") Integer pageNumber) {
        StroopDiagnosisRecordParam query = new StroopDiagnosisRecordParam();
        query.setPageNumber(pageNumber);
        query.setPageSize(pageSize);
        query.setDefaultOrder();
        query.setOrderOperator("DESC");
        return diagnosisRecordMapper.findList(query);
    }

    /**
     * 根据用户id查询他能看到的记录
     *
     * @return
     */
    public List<DiagnosisDetailResult<DiagnosisTaskDetail>> queryPartnerDiagnosisDetailHistory(
            @NotNull(message = "STROOP_RECORD.USER_ID_IS_EMPTY") Long userId,
            @Max(value = 100, message = "STROOP_RECORD.PAGE_SIZE_TOO_LARGE") Integer pageSize,
            @Max(value = 1000, message = "STROOP_RECORD.START_ROW_TOO_LARGE") Integer pageNumber) {
        List<StroopDiagnosisRecordEntity> recordList = queryPartnerDiagnosisHistory(userId, pageSize, pageNumber);
        if (CollectionUtils.isEmpty(recordList)) {
            return new ArrayList<>();
        }
        List<Long> diagnosisIds = new ArrayList<>();
        List<Long> userIds = new ArrayList<>();
        List<DiagnosisDetailResult<DiagnosisTaskDetail>> diagnosisDetailResults = new ArrayList<>();
        for (StroopDiagnosisRecordEntity record : recordList) {
            diagnosisIds.add(record.getId());
            userIds.add(record.getUserId());
            userIds.add(record.getInviteUserId());
        }

        Map<Long, UcUserEntity> userMap = new HashMap<>();
        if (!userIds.isEmpty()) {
            List<UcUserEntity> userList = userService.queryUserByIds(userIds);
            if (!CollectionUtils.isEmpty(userList)) {
                userMap = userList.stream().collect(
                        Collectors.toMap(
                                UcUserEntity::getId, Function.identity(),
                                (key1, key2) -> key2, HashMap::new));
            }
        }
        List<StroopTaskRecordEntity> taskList = diagnosisTaskService.queryTaskRecordByDiagnosisIds(diagnosisIds);
        Map<Long, List<StroopTaskRecordEntity>> taskRecordToMap = wrapTaskRecordToMap(taskList);
        for (StroopDiagnosisRecordEntity record : recordList) {
            DiagnosisDetailResult<DiagnosisTaskDetail> diagnosisDetailResult = getDiagnosisDetailResult(record, taskRecordToMap.get(record.getId()));
            if (userMap.containsKey(diagnosisDetailResult.getUserId())) {
                diagnosisDetailResult.setUserName(userMap.get(diagnosisDetailResult.getUserId()).getName());
                diagnosisDetailResult.setUserAge(userMap.get(diagnosisDetailResult.getUserId()).getAge().toString());
                diagnosisDetailResult.setUserMobile(userMap.get(diagnosisDetailResult.getUserId()).getMobile());
            } else {
                diagnosisDetailResult.setUserName("");
                diagnosisDetailResult.setUserAge("");
                diagnosisDetailResult.setUserMobile("");
            }
            diagnosisDetailResult.setInviterName(userMap.containsKey(diagnosisDetailResult.getInviteUserId()) ?
                    userMap.get(diagnosisDetailResult.getInviteUserId()).getName() : "");
            diagnosisDetailResults.add(diagnosisDetailResult);
        }
        return diagnosisDetailResults;
    }

    /**
     * 根据用户id查询他能看到的记录
     *
     * @return
     */
    public List<DiagnosisFullResult> queryPartnerDiagnosisFullHistory(
            @NotNull(message = "STROOP_RECORD.USER_ID_IS_EMPTY") Long userId,
            @Max(value = 100, message = "STROOP_RECORD.PAGE_SIZE_TOO_LARGE") Integer pageSize,
            @Max(value = 1000, message = "STROOP_RECORD.START_ROW_TOO_LARGE") Integer pageNumber) {
        List<StroopDiagnosisRecordEntity> recordList = queryPartnerDiagnosisHistory(userId, pageSize, pageNumber);
        if (CollectionUtils.isEmpty(recordList)) {
            return new ArrayList<>();
        }
        List<Long> diagnosisIds = new ArrayList<>();
        List<Long> userIds = new ArrayList<>();
        List<DiagnosisFullResult> diagnosisDetailResults = new ArrayList<>();
        for (StroopDiagnosisRecordEntity record : recordList) {
            diagnosisIds.add(record.getId());
            userIds.add(record.getUserId());
            userIds.add(record.getInviteUserId());
        }

        Map<Long, UcUserEntity> userMap = new HashMap<>();
        if (!userIds.isEmpty()) {
            List<UcUserEntity> userList = userService.queryUserByIds(userIds);
            if (!CollectionUtils.isEmpty(userList)) {
                userMap = userList.stream().collect(
                        Collectors.toMap(
                                UcUserEntity::getId, Function.identity(),
                                (key1, key2) -> key2, HashMap::new));
            }
        }
        List<StroopTaskRecordEntity> taskList = diagnosisTaskService.queryTaskRecordByDiagnosisIds(diagnosisIds);
        Map<Long, List<StroopTaskRecordEntity>> taskRecordToMap = wrapTaskRecordToMap(taskList);
        for (StroopDiagnosisRecordEntity record : recordList) {
            DiagnosisFullResult fullResult = getDiagnosisFullResult(record, taskRecordToMap.get(record.getId()));
            UcUserEntity ucUser = userMap.get(fullResult.getUserId());
            if (userMap.containsKey(fullResult.getUserId())) {
                fullResult.setUserName(ucUser.getName());
                fullResult.setUserAge(ucUser.getAge());
                fullResult.setUserMobile(ucUser.getMobile());
                fullResult.setEducation(ucUser.getEducation());
                Gender gender = Gender.getGender(ucUser.getSex());
                fullResult.setUserSex(Optional.ofNullable(gender).orElse(Gender.UNKNOWN).getDesc());
            } else {
                fullResult.setUserName(StringUtils.EMPTY);
                fullResult.setUserAge(0);
                fullResult.setUserMobile(StringUtils.EMPTY);
                fullResult.setEducation(StringUtils.EMPTY);
                fullResult.setUserSex(Gender.UNKNOWN.getDesc());
            }
            fullResult.setInviterName(userMap.containsKey(fullResult.getInviteUserId()) ?
                    userMap.get(fullResult.getInviteUserId()).getName() : "");
            diagnosisDetailResults.add(fullResult);
        }
        return diagnosisDetailResults;
    }

    public Long queryPartnerDiagnosisDetailHistoryCount(
            @NotNull(message = "STROOP_RECORD.USER_ID_IS_EMPTY") Long userId) {
        StroopDiagnosisRecordParam query = new StroopDiagnosisRecordParam();
        query.setPageSize(1000);
        return diagnosisRecordMapper.countList(query);
    }

    @Transactional
    public DiagnosisResult uploadDiagnosisResult(
            @NotNull(message = "UC_USER.USER_ID_EMPTY") Long userId,
            @NotBlank(message = "STROOP.DIAGNOSIS_DATA_IS_EMPTY") String diagnosisPeriod,
            @NotNull(message = "STROOP.STAGE_IS_ENPTY") Integer stage,
            @NotNull(message = "STROOP.START_TIME_IS_ENPTY") Long start) {
        DiagnosisStageEnum diagnosisStage = DiagnosisStageEnum.getDiagnosisStage(stage);
        if (null == diagnosisStage)
            throw new ServiceException("STROOP.STAGE_NOT_FOUND");
        if (!diagnosisPeriod.matches("(\\d.*-\\d)(;(1))*"))
            throw new ServiceException("STROOP.DIAGNOSIS_DATA_NOT_CORRECT");
        StroopTaskRecordPostForm form = new StroopTaskRecordPostForm();
        DiagnosisTask taskResult = getResultCountFromParam(wrapDiagnosisParam(start, diagnosisPeriod));
        form.setTaskCorrectCount(taskResult.getCorrectCount());
        form.setTaskErrorCount(taskResult.getErrorCount());
        form.setTaskId(stage);
        form.setTaskMisCount(taskResult.getMisCount());
        form.setTaskResultParam(diagnosisPeriod);
        form.setTaskStartTime(start);
        form.setUserId(userId);
        form.setTaskTotalTime(taskResult.getTotalTime());
        diagnosisTaskService.addIfNotExistsTaskRecord(form);
        if (stage == DiagnosisStageEnum.getMaxStage()) {
            List<StroopTaskRecordEntity> taskEntityList =
                    diagnosisTaskService.selectByUserTime(userId, start);
//    任务id
            List<DiagnosisTaskBatchEntity> batchIds = new ArrayList<>();

            RootNode[] rootNodeArr = new RootNode[taskEntityList.size()];
            int i = 0;
            for (StroopTaskRecordEntity taskEntity : taskEntityList) {
                DiagnosisTaskBatchEntity batchEntityOne = new DiagnosisTaskBatchEntity();
                batchEntityOne.setTaskId(taskEntity.getId());
                batchEntityOne.setVersion(taskEntity.getVersion());
                batchIds.add(batchEntityOne);
                List<DiagnosisNode> taskNodeList = wrapDiagnosisParam(
                        taskEntity.getTaskStartTime().getTime(), taskEntity.getTaskResultParam());
                RootNode root = new RootNode();
                root.setNodeName(DiagnosisStageEnum.getDiagnosisStage(taskEntity.getTaskId()).getDesc());
                if (null == root.getNodeList()) {
                    root.setNodeList(new ArrayList<>());
                }
                root.getNodeList().addAll(taskNodeList);
                rootNodeArr[i] = root;
                i++;
            }
//    任务的正确数
            Long reactTotal = 0L;
            long stage1Total = 0L;
            int correctCount = 0;
            int totalCount = 0;
            long stageMaxTotal = 0L;
            int srarchStage = 0;
            for (RootNode root : rootNodeArr) {
                List<DiagnosisNode> nodeList = root.getNodeList();
                for (DiagnosisNode node : nodeList) {
                    totalCount++;
                    if (srarchStage == 0) {
                        stage1Total += node.getDuration();
                    }
                    if (srarchStage == rootNodeArr.length - 1) {
                        stageMaxTotal += srarchStage;
                    }
                    if (node.getDiagnosisResponseState() == DiagnosisResponseState.RESPONSE_CORRECT.getCode()) {
                        reactTotal += node.getDuration();
                        correctCount++;
                    }
                }
                srarchStage++;
            }

            StroopDiagnosisRecordEntity entity = new StroopDiagnosisRecordEntity();
            entity.setDiagnosisCorrectInterference(
                    taskEntityList.get(taskEntityList.size() - 1).getTaskCorrectCount() - taskEntityList.get(0).getTaskCorrectCount());
            entity.setDiagnosisDelay((int) (
                    stageMaxTotal / (rootNodeArr[rootNodeArr.length - 1].getNodeList().size()) -
                            (stage1Total / (rootNodeArr[0].getNodeList().size()))));
            //
            int score = (100 * correctCount / totalCount);
            entity.setDiagnosisLevel(DiagnosisLevelEnum.setDiagnosisLevel(score));

            entity.setDiagnosisReactPeriod(correctCount > 0 ? (int) (reactTotal / correctCount) : 0);
            entity.setDiagnosisScore(score);
            entity.setUserId(userId);
            //查询当前用户是否是被邀请的用户
            StroopInvitationEntity inviterInfo = invitationServce.queryUserInviter(userId);
            if (null != inviterInfo) {
                entity.setInviteId(inviterInfo.getId());
                entity.setInviteUserId(inviterInfo.getInviterId());
            } else {
                entity.setInviteId(0l);
                entity.setInviteUserId(0l);
            }

            diagnosisRecordMapper.insert(entity);
            diagnosisTaskService.updateStroopTaskById(batchIds, entity.getId());
            entity.setCreateDate(new Date());
            return getDiagnosisResult(entity, taskEntityList);
        }
        return null;
    }

    public DiagnosisResult queryDiagnosisDetail(
            @NotNull(message = "STROOP.DIAGNOSIS_ID_IS_EMPTY") Long diagnosisId) {
        StroopDiagnosisRecordEntity entity = diagnosisRecordMapper.findOne(diagnosisId);
        if (null == entity)
            throw new ServiceException("STROOP_RECORD.RECORD_IS_EMPTY");
        List<StroopTaskRecordEntity> taskList =
                diagnosisTaskService.queryTaskRecordByDiagnosisId(diagnosisId);
        return getDiagnosisDetailResult(entity, taskList);
    }

    private DiagnosisResult<DiagnosisTask> getDiagnosisResult(
            StroopDiagnosisRecordEntity entity, List<StroopTaskRecordEntity> taskList) {
        DiagnosisResult<DiagnosisTask> result = new DiagnosisResult<>();
        wrapResult(result, entity);
        wrapResultTask(result, taskList);
        return result;
    }

    private DiagnosisDetailResult<DiagnosisTaskDetail> getDiagnosisDetailResult(
            StroopDiagnosisRecordEntity entity, List<StroopTaskRecordEntity> taskList) {
        DiagnosisDetailResult<DiagnosisTaskDetail> result = new DiagnosisDetailResult<>();
        wrapResult(result, entity);
        result.setUserId(entity.getUserId());
        result.setInviteUserId(entity.getInviteUserId());
        wrapDetailResultTask(result, taskList);
        return result;
    }

    private DiagnosisFullResult getDiagnosisFullResult(
            StroopDiagnosisRecordEntity entity, List<StroopTaskRecordEntity> taskList) {
        DiagnosisFullResult result = new DiagnosisFullResult();
        wrapFullResult(result, entity);
        result.setUserId(entity.getUserId());
        result.setInviteUserId(entity.getInviteUserId());
        wrapFullResultTask(result, taskList);
        return result;
    }

    private void wrapResult(DiagnosisResult<? extends DiagnosisTask> result, StroopDiagnosisRecordEntity entity) {
        if (null == result || null == entity)
            return;
        result.setDelayPeriod(entity.getDiagnosisDelay());
        result.setDiagnosisDate(DateTools.getFullDate(entity.getCreateDate()));
        result.setDiagnosisId(entity.getId());
        result.setDiagnosisLevel(DiagnosisLevelEnum.getDiagnosisLevelDesc(entity.getDiagnosisLevel()));
        result.setInterference(entity.getDiagnosisCorrectInterference());
        result.setReactPeriod(entity.getDiagnosisReactPeriod());
        result.setTaskList(new ArrayList<>());
    }

    private void wrapFullResult(DiagnosisFullResult result, StroopDiagnosisRecordEntity entity) {
        if (null == result || null == entity)
            return;
        result.setDelayPeriod(entity.getDiagnosisDelay());
        result.setDiagnosisDate(DateTools.getFullDate(entity.getCreateDate()));
        result.setDiagnosisId(entity.getId());
        result.setDiagnosisLevel(DiagnosisLevelEnum.getDiagnosisLevelDesc(entity.getDiagnosisLevel()));
        result.setInterference(entity.getDiagnosisCorrectInterference());
        result.setReactPeriod(entity.getDiagnosisReactPeriod());
    }

    private void wrapResultTask(DiagnosisResult<DiagnosisTask> result, List<StroopTaskRecordEntity> taskList) {
        if (null != taskList && !taskList.isEmpty())
            for (StroopTaskRecordEntity task : taskList) {
                DiagnosisTask diagnosisTask = new DiagnosisTask();
                wrapDetailResultTask(diagnosisTask, task);
                result.getTaskList().add(diagnosisTask);
            }
    }

    private void wrapFullResultTask(DiagnosisFullResult result, List<StroopTaskRecordEntity> taskList) {
        if (null == taskList || taskList.isEmpty()) {
            return;
        }
        int i = 0;
        for (StroopTaskRecordEntity task : taskList) {
            if (i == 0) {
                result.setACorrect(task.getTaskCorrectCount());
                result.setAWrong(task.getTaskErrorCount());
                result.setAMiss(task.getTaskMisCount());
                result.setATime(task.getTaskTotalTime());
                i++;
                continue;
            }
            if (i == 1) {
                result.setBCorrect(task.getTaskCorrectCount());
                result.setBWrong(task.getTaskErrorCount());
                result.setBMiss(task.getTaskMisCount());
                result.setBTime(task.getTaskTotalTime());
                i++;
                continue;
            }
            if (i == 2) {
                result.setCCorrect(task.getTaskCorrectCount());
                result.setCWrong(task.getTaskErrorCount());
                result.setCMiss(task.getTaskMisCount());
                result.setCTime(task.getTaskTotalTime());
                i++;
            }
        }
    }

    private void wrapDetailResultTask(DiagnosisDetailResult result, List<StroopTaskRecordEntity> taskList) {
        if (null != taskList && !taskList.isEmpty())
            for (StroopTaskRecordEntity task : taskList) {
                DiagnosisTaskDetail diagnosisTask = new DiagnosisTaskDetail();
                wrapDetailResultTask(diagnosisTask, task);
                diagnosisTask.setOriginData(task.getTaskResultParam());
                diagnosisTask.setTotalTime(task.getTaskTotalTime());
                result.getTaskList().add(diagnosisTask);
            }
    }

    private void wrapDetailResultTask(DiagnosisTask diagnosisTask, StroopTaskRecordEntity task) {
        diagnosisTask.setCorrectCount(task.getTaskCorrectCount());
        diagnosisTask.setErrorCount(task.getTaskErrorCount());
        diagnosisTask.setMisCount(task.getTaskMisCount());
        diagnosisTask.setTaskName(
                DiagnosisStageEnum.getDiagnosisStage(task.getTaskId()).getDesc());
    }

    private Map<Long, List<StroopTaskRecordEntity>> wrapTaskRecordToMap(List<StroopTaskRecordEntity> taskList) {
        Map<Long, List<StroopTaskRecordEntity>> taskMap = new HashMap<>();
        if (CollectionUtils.isEmpty(taskList)) {
            return taskMap;
        }
        for (StroopTaskRecordEntity record : taskList) {
            if (!taskMap.containsKey(record.getDiagnosisId())) {
                taskMap.put(record.getDiagnosisId(), new ArrayList<>());
            }
            taskMap.get(record.getDiagnosisId()).add(record);
        }
        return taskMap;
    }

    private List<Long> getUserInvites(List<StroopInvitationEntity> invitationList) {
        if (null == invitationList) {
            return null;
        }
        List<Long> list = new ArrayList<>();
        for (StroopInvitationEntity entity : invitationList) {
            list.add(entity.getId());
        }
        return list;
    }

    private List<DiagnosisNode> wrapDiagnosisParam(Long startTime, String diagnosisPeriod) {
        String[] periods = getPeriods(diagnosisPeriod);
        List<DiagnosisNode> list = new ArrayList<>();
        for (String period : periods) {
            String[] nodeParam = getDiagnosisNode(period);
            DiagnosisNode node = new DiagnosisNode();
            node.setDiagnosisResponseState(Integer.parseInt(nodeParam[1]));
            node.setDuration(Long.parseLong(nodeParam[0]));
            node.setBeginTime(startTime);
            list.add(node);
        }
        return list;
    }

    private DiagnosisTask getResultCountFromParam(List<DiagnosisNode> nodeList) {
        DiagnosisTask task = new DiagnosisTask();
        int takeTime = 0;
        for (DiagnosisNode node : nodeList) {
            int responseCode = node.getDiagnosisResponseState();
            DiagnosisResponseState responseState =
                    DiagnosisResponseState.getDiagnosisResponseState(responseCode);
            if (null == responseState) {
                throw new ServiceException("STROOP_RECORD.DIAGNOSIS_STAE_NOT_CORRECT");
            }
            if (responseCode == DiagnosisResponseState.NO_RESPONSE.getCode())
                task.setMisCount(task.getMisCount() + 1);
            if (responseCode == DiagnosisResponseState.RESPONSE_CORRECT.getCode())
                task.setCorrectCount(task.getCorrectCount() + 1);
            if (responseCode == DiagnosisResponseState.RESPONSE_WRONG.getCode())
                task.setErrorCount(task.getErrorCount() + 1);
            takeTime += node.getDuration();
        }
        task.setTotalTime(takeTime);
        return task;
    }

    private String[] getPeriods(String diagnosisPeriod) {
        String[] periods = diagnosisPeriod.split(";");
        if (null == periods || periods.length == 0) {
            throw new ServiceException("STROOP.DIAGNOSIS_DATA_NOT_CORRECT");
        }
        return periods;
    }

    private String[] getDiagnosisNode(String period) {
        String[] timeSet = period.split("-");
        if (null == timeSet || timeSet.length != 2) {
            throw new ServiceException("STROOP.DIAGNOSIS_DATA_NOT_CORRECT");
        }
        return timeSet;
    }
}
